/*
 * Copyright ?2006, Atachiants Roman
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 *    - Redistributions of source code must retain the above copyright notice, 
 *      this list of conditions and the following disclaimer.
 * 
 *    - Redistributions in binary form must reproduce the above copyright notice, 
 *      this list of conditions and the following disclaimer in the documentation 
 *      and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 */
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Threading;
using ui.listview.header;
using ui.listview.sorter;

namespace ui.listview
{
    public class ExListView : ListView
    {
        #region Fields
        private ToolStrip tsGroups;
        private Image m_ToolStripImage = null;
        private ArrayList HeaderGroup = new ArrayList();
        private bool m_ShowGroupLabel = false;
        private string m_GroupLabelText = "Group by :";
        private bool m_GroupsGUIs = false;

        #endregion

        #region Properties
        [Category("Groups")]
        [Description("Show or not the ToolStrip to allow group items.")]
        public bool GroupsGUIs
        {
            get { return m_GroupsGUIs; }
            set { m_GroupsGUIs = value; Bind(); }
        }

        [Category("Groups")]
        [Description("The image to show on ToolStripButtons of the groups")]
        public Image ToolStripImage
        {
            get { return m_ToolStripImage; }
            set { m_ToolStripImage = value; CreateButtons(); }
        }

        [Category("Groups")]
        [Description("Show or hide the 'ShowGroup' label.")]
        public bool ShowGroupLabel
        {
            get { return m_ShowGroupLabel; }
            set { m_ShowGroupLabel = value; CreateButtons(); }
        }

        [Category("Groups")]
        [Description("The 'ShowGroup' label. (DEFAULT : 'Group by:'")]
        public string GroupLabelText
        {
            get { return m_GroupLabelText; }
            set { m_GroupLabelText = value; }
        }
        #endregion

        #region Constructors
        public ExListView()
        {
            InitializeComponent();
            Bind();
        }

        #endregion

        #region Overrides
        protected override void OnCreateControl()
        {
            base.OnCreateControl();
            Bind();
            CreateButtons();
        }

        protected override void OnMove(EventArgs e)
        {
            base.OnMove(e);
            Bind();
        }
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            Bind();
            
        }

        protected override void Dispose(bool disposing)
        {
            this.tsGroups.Dispose();
            base.Dispose(disposing);
        }

        public override DockStyle Dock
        {
            get { return base.Dock; }
            set
            {
                base.Dock = value; 
                if (value == DockStyle.Fill || value == DockStyle.Top)
                {
                    tsGroups.Dock = DockStyle.Top;
                }
                else
                {
                    tsGroups.Dock = DockStyle.None;
                }
            }
        }

    #endregion

        #region Binding
        protected virtual void Bind()
        {
            if (m_GroupsGUIs)
            {
                if (this.tsGroups.Visible==false)
                    this.tsGroups.Visible = true;
                this.tsGroups.Parent = this.Parent;
                this.tsGroups.Location = new Point(this.Location.X, this.Location.Y - 25);
                if (tsGroups.Width != this.Width)
                    this.tsGroups.Width = this.Width;
            }
            else
            {
                if (this.tsGroups.Visible)
                    this.tsGroups.Visible = false;
            }
        }
        #endregion

        #region Group Methods

        #region Thread-Safe GroupBy Method
        delegate void dGroupBy(ColumnHeader[] Headers);
        public void GroupBy(ColumnHeader[] Headers)
        {
            if (this.InvokeRequired)
            {
                dGroupBy d = new dGroupBy(GroupBy);
                this.Invoke(d, new object[] { Headers });
            }
            else
            {
                //code
                foreach (ListViewItem lvi in this.Items)
                {
                    string header = "";

                    foreach (ColumnHeader ch in Headers)
                    {
                        header += " " + lvi.SubItems[ch.Index].Text;
                    }

                    ListViewGroup group = new ListViewGroup(header);
                    ListViewGroup found = null;
                    foreach (ListViewGroup g in Groups)
                    {
                        if (g.Header == group.Header)
                        { found = g; break; }
                    }
                    if (found == null)
                    {
                        this.Groups.Add(group);
                        group.Items.Add(lvi);
                    }
                    else
                    {
                        found.Items.Add(lvi);
                    }


                }
            }
        }
        #endregion

        public virtual void GroupBy()
        {
            GroupBy((ColumnHeader[])HeaderGroup.ToArray(typeof(ColumnHeader)));
        }

    
        /// <summary>
        /// Method for generation ToolStrip Buttons on GroupBy ToolStrip
        /// </summary>
        private void CreateButtons()
        {

            this.tsGroups.Items.Clear();
            if (m_ShowGroupLabel)
            {
                ToolStripLabel tsl = new ToolStripLabel(m_GroupLabelText);
                tsGroups.Items.Add(tsl);
            }

            foreach (ColumnHeader C in this.Columns)
            {
                ToolStripButton tsb = new ToolStripButton(C.Text,m_ToolStripImage);
                tsb.Click += new EventHandler(tsb_Click);
                tsb.Tag = C;
                tsGroups.Items.Add(tsb);
            }
        }

        void tsb_Click(object sender, EventArgs e)
        {
            ToolStripButton tsb = ((ToolStripButton)sender);
            
            tsb.Checked = !tsb.Checked;
            if(tsb.Checked)
            {
                HeaderGroup.Add((ColumnHeader)tsb.Tag);
            }else{
                HeaderGroup.Remove((ColumnHeader)tsb.Tag);
            }

            Thread th = new Thread(new ThreadStart(GroupBy));
            th.IsBackground = true;
            th.Start();
        }

        #endregion

        #region GeneratedCode
        private void InitializeComponent()
        {
            this.tsGroups = new System.Windows.Forms.ToolStrip();
            this.SuspendLayout();
            // 
            // tsGroups
            // 
            this.tsGroups.AutoSize = false;
            this.tsGroups.Dock = System.Windows.Forms.DockStyle.None;
            this.tsGroups.Location = new System.Drawing.Point(0, 0);
            this.tsGroups.Name = "tsGroups";
            this.tsGroups.Size = new System.Drawing.Size(100, 25);
            this.tsGroups.TabIndex = 0;
            this.tsGroups.Text = "toolStrip1";
            this.ResumeLayout(false);

        }
        #endregion

        
        /// <summary date="16-04-2005, 22:04:14" author="S.Deckers">
        /// Get a sorter for the type specified in dataColumn.DataType
        /// </summary>
        /// <param name="dataColumn"></param>
        /// <returns>A columnheader with support for sorting</returns>
        private ListViewColumnHeader getListViewColumnHeader(ui.data.DataTableColumn dataColumn)
        {
            System.Type type = dataColumn.DataType;

            ListViewColumnHeader sortableListviewColumnHeader = new ListViewColumnHeader();

            string s = string.Empty;

            // --- String -> ListViewTextCaseInsensitiveSort

            if (type == typeof(System.String))
            {
                sortableListviewColumnHeader.ListviewSorter = new ListViewTextCaseInsensitiveSorter();
                return (sortableListviewColumnHeader);
            }

            // --- System.Int32 -> ListViewInt32Sort

            else if (type == typeof(System.Int32))
            {
                sortableListviewColumnHeader.ListviewSorter = new ListViewInt32Sorter();
                return (sortableListviewColumnHeader);
            }

            // --- System.Decimal, System.Double -> ListViewDoubleSort

            else if (type == typeof(System.Decimal) ||
                     type == typeof(System.Double))
            {
                sortableListviewColumnHeader.ListviewSorter = new ListViewDoubleSorter();
                return (sortableListviewColumnHeader);
            }

            // --- System.DateTime -> ListViewDateSort

            else if (type == typeof(System.DateTime))
            {
                sortableListviewColumnHeader.ListviewSorter = new ListViewDateSorter();
                return (sortableListviewColumnHeader);
            }

            // --- This type is not handled

            else
            {
                string err = string.Format("Type [{0}] is not implemented for sorting", dataColumn.DataType);
                throw new System.NotImplementedException(err);
            }
        }

        #region -- Methods --

        /// <summary>
        /// Tablename for storing columnwidths
        /// </summary>
        private string _previousTable = string.Empty;

        /// <summary>
        /// DataBind
        /// </summary>
        public void DataBind()
        {
            if (this.DataTable == null)
            {
                throw new System.Exception("No DataTable. Please set the DataTable once before calling DataBind");
            }

            if (this.Columns.Count == 0)
            {
                throw new System.Exception("No columns. Please call CreateColumns once before calling DataBind");
            }

            this.BeginUpdate();
            this.Items.Clear();

            // --- It there has been a sorter set for this listview, the listview
            //     will invoke th System.Collections.IComparer.Compare-method on it.
            //     When changing datatables, the old-comparere will still be assigned
            //     to the listview which may cause unexpected results (20050416 SDE)

            this.ListViewItemSorter = null;
            Data2Listview(this.DataTable);
            AdjustColumnWidths();
            this.EndUpdate();
        }

        #endregion

        #region -- Properties --

        #region -- DataTable property --
        /// <summary>
        /// The datatable to retrieve data from. Calling the setter of this property will create the
        /// Listview-columns
        /// </summary>
        private ui.data.DataTable _dataTable;
        
        [Bindable(true),
         Category("ui.listview"),
         Description("DataTable")]
        public ui.data.DataTable DataTable
        {
            get { return (_dataTable); }
            set
            {
                if (_dataTable == value)
                {
                    return;
                }
                _dataTable = value;
                this.CreateListViewColumns(_dataTable);
            }
        }
        #endregion

        #endregion

        /// <summary date="10-03-2005, 21:03:18" author="S.Deckers">
        /// Create columns according to column-names as found in the datatable
        /// </summary>
        /// <param name="dataTable"></param>
        private void CreateListViewColumns(ui.data.DataTable dataTable)
        {
            ColumnHeader[] columnHeaders = new ColumnHeader[dataTable.Headers.Count];
            ColumnHeader columnHeader = null;

            this.Columns.Clear();

            // --- Enumerate DataColumns and create ColumnHeaders for them (20050418 SDE)

            int i = 0;
            foreach (ui.data.DataTableColumn dataColumn in dataTable.Headers)
            {
                columnHeader = getListViewColumnHeader(dataColumn);
                columnHeader.Text = dataColumn.Text;
                columnHeader.TextAlign = dataColumn.Alignment;
                columnHeaders[i] = columnHeader;
                i++;
            }

            // --- Tell listview to create the columns (20050309 SDE)

            this.Columns.AddRange(columnHeaders);
        }

        /// <summary date="15-05-2005, 17:05:07" author="S.Deckers">
        /// If the user has not modified the width this method will adjust the columnwidht to 
        /// the largest item in the list, or the width of the columnheader, if it is wider. If 
        /// the user has modified a columnwidth, the widht is not changed
        /// </summary>
        /// <remarks>Algoritme over here</remarks>
        private void AdjustColumnWidths()
        {
            string s = string.Empty;

            foreach (ListViewColumnHeader slc in this.Columns)
            {
                // --- If the PreviousWidth equals the Width, the user hasn't modified a columns
                //     width and we need to autofit it to the largest item. Otherwise, 
                //     PreviousWidth is not equal to the width, the user has modified the width
                //     of a column, and we need to leave it the way it is (20050515 SDE)

                if (slc.PreviousWidth == slc.Width || slc.PreviousWidth == 0)
                {
                    slc.Width = System.Convert.ToInt32(slc.LargestSize);
                    slc.PreviousWidth = slc.Width;
                }
            }
        }

        /// <summary>
        /// Fill listview using all columns
        /// </summary>
        /// <param name="dt"></param>
        private void Data2Listview(ui.data.DataTable dt)
        {
            System.Diagnostics.Debug.Assert(dt.Headers.Count == this.Columns.Count, string.Format("Columncount != listview columncount"));

            System.Windows.Forms.ListViewItem listViewItem = null;

            // --- Add a ListView-items for each row and see if it has the largest string-length
            //     of items so we can fit the columnheader (20050228 SDE)

            Graphics g = this.CreateGraphics();

            string item = string.Empty;
            int row = 0, col = 0;
            foreach (Dictionary<string, object> dr in dt.Rows)
            {
                ArrayList items = new ArrayList();
                ListViewColumnHeader slc = null;
                float itemWidth = 0.0F;
                float columnWidth = 0.0F;
                float width = 0.0F;
                int itemLength = 0;
                col = 0;
                foreach (ui.data.DataTableColumn dataColumn in dt.Headers)
                {
                    item = dt.getValue(col, row).ToString().Trim();

                    items.Add(item);
                    slc = (ListViewColumnHeader)this.Columns[col];

                    // --- If the widht of a columnheader is larger then the largest item in the
                    //     list, we use the columnheader (20050515 SDE)

                    columnWidth = MeasureDisplayStringWidth(g, slc.Text, this.Font);

                    // --- Length item (20050515 SDE)

                    itemLength = item.Length;
                    if (item.Trim() != string.Empty)
                    {
                        itemWidth = MeasureDisplayStringWidth(g, item, this.Font);

                        if (itemWidth > columnWidth)
                        {
                            width = itemWidth;
                        }
                        else
                        {
                            width = columnWidth;
                        }

                        if (width > slc.LargestSize)
                        {
                            slc.LargestSize = width;
                        }
                    }

                    // --- Use columnwidth when values are null (20050515 SDE)

                    else
                    {
                        width = columnWidth;

                        if (width > slc.LargestSize)
                        {
                            slc.LargestSize = width;
                        }
                    }
                    col++;
                }
                row++;

                string[] listItems = (string[])items.ToArray(typeof(string));
                listViewItem = new ListViewItem(listItems);
                this.Items.Add(listViewItem);
            }
        }

        /// <summary>
        /// Determine length of string
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="text"></param>
        /// <param name="font"></param>
        /// <returns></returns>
        private float MeasureDisplayStringWidth(Graphics graphics, string text, Font font)
        {
            const float offSet = 25.0F; // --- Offset for borders (20050515 SDE)
            System.Drawing.StringFormat format = new System.Drawing.StringFormat();
            System.Drawing.RectangleF rect = new System.Drawing.RectangleF(0, 0, 1000, 1000);
            System.Drawing.CharacterRange[] ranges = { new System.Drawing.CharacterRange(0, text.Length) };
            System.Drawing.Region[] regions = new System.Drawing.Region[1];

            format.SetMeasurableCharacterRanges(ranges);

            regions = graphics.MeasureCharacterRanges(text, font, rect, format);
            rect = regions[0].GetBounds(graphics);

            return (rect.Right + offSet);
        }

        /// <summary date="16-04-2005, 21:04:03" author="S.Deckers">
        /// A column was clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ListView_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            // --- Perform sorting

            System.Windows.Forms.ListView listview = sender as ListView;

            ListViewColumnHeader sorter = listview.Columns[e.Column] as ListViewColumnHeader;

            if (listview.Sorting == SortOrder.None)
            {
                listview.Sorting = SortOrder.Ascending;
            }
            else if (listview.Sorting == SortOrder.Ascending)
            {
                listview.Sorting = SortOrder.Descending;
            }
            else
            {
                listview.Sorting = SortOrder.Ascending;
            }

            sorter.Column = e.Column;
            this.ListViewItemSorter = sorter;
        }

    }
}
